﻿using BizTalk.ESB.Extensions.Helpers;
using Microsoft.Practices.ESB.Adapter;
using Microsoft.Practices.ESB.Exception.Management;
using Microsoft.Practices.ESB.Itinerary;
using Microsoft.Practices.ESB.Itinerary.Services;
using Microsoft.Practices.ESB.Resolver;
using Microsoft.BizTalk.Bam.EventObservation;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.Streaming;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Xsl;
using System.Xml.XPath;

namespace BizTalk.ESB.Extensions.MessagingServices
{
    public class BusinessDataMonitor : IMessagingService
    {
        private string _BizTalkSystemNamespace = @"http://schemas.microsoft.com/BizTalk/2003/system-properties";

        public IBaseMessage Execute(IPipelineContext context, IBaseMessage msg, string resolverString, IItineraryStep step)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            if (msg == null)
                throw new ArgumentNullException("msg");
            if (string.IsNullOrEmpty(resolverString))
                throw new ArgumentException("Resolver must be provided", "resolverString");
            if (step == null)
                throw new ArgumentNullException("step");

            try
            {
                System.Diagnostics.Trace.WriteLine("BusinessDataMonitor - Step UUID = " + step.UUId);
                System.Diagnostics.Trace.WriteLine("BusinessDataMonitor - Step ID = " + step.Id);
                System.Diagnostics.Trace.WriteLine("BusinessDataMonitor - Service Instance ID = " + step.ServiceInstanceId);

                ResolverInfo info = ResolverMgr.GetResolverInfo(ResolutionType.Endpoint, resolverString);
                if (!info.Success)
                    throw new RoutingException("Invalid Resolver", resolverString);

                // Resolve configuration for routing.
                Dictionary<string, string> resolverDictionary = ResolverMgr.Resolve(info, msg, context);
                

                var bamType = resolverDictionary["Resolver.BamActivityDefinitionName"];
                System.Diagnostics.Trace.WriteLine("BusinessDataMonitor - Resolver.BamActivityDefinitionName = " + bamType);

                BamEventTypes bamEventStreamType = (BamEventTypes)Enum.Parse(typeof(BamEventTypes), resolverDictionary["Resolver.BamEventType"]);
                System.Diagnostics.Trace.WriteLine("BusinessDataMonitor - Resolver.BamEventType = " + bamEventStreamType.ToString());

                var bamDataConfig = resolverDictionary["Resolver.TrackingConfig"];
                System.Diagnostics.Trace.WriteLine("BusinessDataMonitor - Resolver.TrackingConfig = " + bamDataConfig);

                int bamFlushThreshold = int.Parse(resolverDictionary["Resolver.FlushThreshold"]);
                System.Diagnostics.Trace.WriteLine("BusinessDataMonitor - Resolver.FlushThreshold = " + bamFlushThreshold.ToString());

                bool stopOnError = bool.Parse(resolverDictionary["Resolver.StopOnError"]);
                System.Diagnostics.Trace.WriteLine("BusinessDataMonitor - Resolver.StopOnError = " + stopOnError.ToString());

                int bamTimeout = int.Parse(resolverDictionary["Resolver.Timeout"]);
                System.Diagnostics.Trace.WriteLine("BusinessDataMonitor - Resolver.Timeout = " + bamTimeout.ToString());

                var bamConnString = resolverDictionary["Resolver.BAMConnectionString"];
                System.Diagnostics.Trace.WriteLine("BusinessDataMonitor - Resolver.BAMConnectionString = " + bamConnString);

                var msgboxConnString = resolverDictionary["Resolver.MsgBoxConnectionString"];
                System.Diagnostics.Trace.WriteLine("BusinessDataMonitor - Resolver.MsgBoxConnectionString = " + msgboxConnString);
                
                if (msg.BodyPart != null && (!string.IsNullOrEmpty(bamDataConfig)))
                {
                    var originalStrm = msg.BodyPart.GetOriginalDataStream();

                    if (originalStrm != null)
                    {
                        // Monitor Business Data
                        var connString = bamEventStreamType == BamEventTypes.Synchronous ? bamConnString : msgboxConnString;

                        try
                        {
                            TrackBam(msg, context, step, bamType, bamEventStreamType, bamDataConfig, bamFlushThreshold, bamTimeout
                                , connString, stopOnError);
                        }
                        catch (SystemException exBam)
                        {
                            if (stopOnError)
                            {
                                throw exBam;
                            }
                        }
                    }
                }

                return msg;
            }
            catch (System.Exception ex)
            {
                EventLogger.Write(MethodInfo.GetCurrentMethod(), ex);
                throw;
            }    
        }

        public string Name
        {
            get { return "BizTalk.ESB.Extensions.MessagingServices.BusinessDataMonitor"; }
        }

        public bool ShouldAdvanceStep(IItineraryStep step, IBaseMessage msg)
        {
            return true;
        }

        public bool SupportsDisassemble
        {
            get { return false; }
        }

        #region Private Methods

        //private object[] BuildTrackingConfigArray(string BamTrackingConfig, XmlDocument Message, IBaseMessageContext Context)
        //{
        //    var config = new List<string>();
        //    if (!string.IsNullOrEmpty(BamTrackingConfig))
        //    {
        //        var mappingList = BamTrackingConfig.Split("&".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
        //        foreach (string fieldMapping in mappingList)
        //        {
        //            var fieldKey = fieldMapping.Substring(0, fieldMapping.IndexOf("="));
        //            var fieldValueSource = fieldMapping.Substring(fieldMapping.IndexOf("=") + 1);
        //            if (!string.IsNullOrEmpty(fieldKey) && !string.IsNullOrEmpty(fieldValueSource))
        //            {
        //                var fieldValue = GetBamTrackingSource(fieldValueSource, Message, Context);
        //                config.Add(fieldKey);
        //                config.Add(fieldValue);
        //                System.Diagnostics.Trace.WriteLine("BusinessDataMonitor - Added BAM Tracking: Field = " + fieldKey
        //                    + " and Value/Mapping = " + fieldValue);
        //            }
        //            else
        //            {
        //                EventLogger.Write("Encountered an invalid BAM Tracking configuration: " + fieldMapping, MethodInfo.GetCurrentMethod());
        //            }
        //        }
        //    }
        //    return config.ToArray<object>();
        //}

        //private string GetBamTrackingSource(string SourceKey, XmlDocument Message, IBaseMessageContext Context)
        //{
        //    string output = string.Empty;
        //    if (!string.IsNullOrEmpty(SourceKey))
        //    {
        //        if (SourceKey.Contains("{"))
        //        {
        //            var key = SourceKey.TrimEnd("}".ToCharArray());
        //            var keyPrefix = key.Substring(0, key.IndexOf("{"));
        //            var keyValue = key.Substring(key.IndexOf("{") + 1);
        //            switch (keyPrefix)
        //            {
        //                case "XPATH":
        //                    {
        //                        if (IsValidXpath(keyValue))
        //                        {
        //                            output = Message.SelectSingleNode(keyValue).InnerText;
        //                        }
        //                        break;
        //                    }
        //                case "CONTEXT":
        //                    {
        //                        output = GetContextPropertyValue(Context, keyValue);
        //                        break;
        //                    }
        //                default:
        //                    {
        //                        output = keyValue;
        //                        break;
        //                    }
        //            }
        //        }
        //        else
        //        {
        //            switch (SourceKey)
        //            {
        //                case "NOW":
        //                    {
        //                        output = DateTime.UtcNow.ToString();
        //                        break;
        //                    }
        //                case "GUID":
        //                    {
        //                        output = Guid.NewGuid().ToString();
        //                        break;
        //                    }
        //                default:
        //                    {
        //                        output = SourceKey;
        //                        break;
        //                    }
        //            }
        //        }

        //    }
        //    else
        //    {
        //        EventLogger.Write("Encountered an invalid BAM Tracking configuration: " + SourceKey, MethodInfo.GetCurrentMethod());
        //    }
        //    return output;
        //}

        //private string GetContextPropertyValue(IBaseMessageContext Context, string keyValue)
        //{
        //    var output = string.Empty;
        //    if (!string.IsNullOrEmpty(keyValue))
        //    {
        //        var splitLocation = keyValue.LastIndexOf(@"/");
        //        var propNamespace = keyValue.Substring(0, splitLocation);
        //        var propName = keyValue.Substring(splitLocation + 1);

        //        output = (string) Context.Read(propName, propNamespace);
        //    }
        //    return output;
        //}

        //private bool IsValidXpath(string keyValue)
        //{
        //    if (!string.IsNullOrEmpty(keyValue))
        //    {
        //        try
        //        {
        //            var xpathValue = XPathExpression.Compile(keyValue);
        //            return true;
        //        }
        //        catch (XPathException)
        //        {
        //            return false;
        //        }
        //    }
        //    else
        //    {
        //        return false;
        //    }
        //}

        private void TrackBam(IBaseMessage Message, IPipelineContext PipeContext, IItineraryStep ItineraryStep, string BamTypeName, BamEventTypes BamEventType
            , string BamTackingConfigString, int BamFlushThreshold, int TimeoutSeconds, string BamConnectionString, bool StopOnError)
        {
            if (String.IsNullOrEmpty(BamTypeName))
            {
                throw new ArgumentException("BAM Activity Definition name cannot be null", "BamTypeName");
            }
            else if (String.IsNullOrEmpty(BamTackingConfigString))
            {
                throw new ArgumentException("BAM Event Type name cannot be null", "BamEventType");
            }
            else
            {
                var dataTracker = new BusinessDataTracking();
                var xmlMsg = new XmlDocument();
                xmlMsg.LoadXml(this.GetMsgContent(PipeContext, Message));
                dataTracker.TrackBam(xmlMsg, Message.Context, PipeContext, ItineraryStep, BamTypeName, BamEventType
                    , BamTackingConfigString, BamFlushThreshold, TimeoutSeconds, BamConnectionString, StopOnError);

                //var activityId = ItineraryStep.ServiceInstanceId;
                //var xmlMsg = new XmlDocument();
                //xmlMsg.LoadXml(this.GetMsgContent(MsgContext, Message));
                //var bamTrackingConfig = BuildTrackingConfigArray(BamTackingConfigString, xmlMsg, Message.Context);

                //System.Diagnostics.Trace.WriteLine("BusinessDataMonitor - Building BAM Writer...");
                //BamWriter bamWriter;
                //if (BamEventType == BamEventTypes.Integrated)
                //{
                //    bamWriter = new BamWriter(MsgContext);
                //}
                //else
                //{
                //    bamWriter = new BamWriter(BamEventType, BamConnectionString, BamFlushThreshold, TimeoutSeconds);
                //}
                //bamWriter.BeginActivity(BamTypeName, activityId);
                //System.Diagnostics.Trace.WriteLine("BusinessDataMonitor - BeginActivity = " + activityId);
                //bamWriter.UpdateActivity(BamTypeName, activityId, bamTrackingConfig);
                //System.Diagnostics.Trace.WriteLine("BusinessDataMonitor - UpdateActivity = " + activityId);
                //bamWriter.EndActivity(BamTypeName, activityId);
                //System.Diagnostics.Trace.WriteLine("BusinessDataMonitor - EndActivity = " + activityId);
                //if (BamFlushThreshold <= 0)
                //{
                //    bamWriter.Flush();
                //    System.Diagnostics.Trace.WriteLine("BusinessDataMonitor - Flush = " + activityId);
                //}
            }
        }

        private String GetMessageContent(IPipelineContext pContext, IBaseMessage message)
        {
            //var messageSB = new StringBuilder(); //Save message in this StringBuilder
            var copyStream = new VirtualStream(); //Copy the original stream to the a new stream 
            var copyStreamWriter = new StreamWriter(copyStream); //Use a stream writer to copy original stream to copyStream
            ReadOnlySeekableStream seekableStream = new ReadOnlySeekableStream(message.BodyPart.GetOriginalDataStream());

            String msgData = string.Empty;
            //Stream outStream = new VirtualStream();
            //StreamWriter strmWriter = new StreamWriter(outStream);

            using (StreamReader sr = new
                          StreamReader(message.BodyPart.GetOriginalDataStream()))
            {
                msgData = sr.ReadToEnd();
                //strmWriter.Flush(); // flush writer to ensure writing's done.
                //strmWriter.Close(); // close stream since it won't be used
                sr.Close();
            }

            //reset stream
            copyStreamWriter.Flush(); //Ensure everything has been written to the new stream
            copyStream.Seek(0, SeekOrigin.Begin); //Set the new stream to the beginning
            message.BodyPart.Data = copyStream; //Set the Stream of the message leaving it's integrity intact

            pContext.ResourceTracker.AddResource(copyStream); //Ensure these resources are tracked and ultimately disposed
            pContext.ResourceTracker.AddResource(copyStreamWriter); //Ensure these resources are tracked and ultimately disposed

            return msgData;
        }

        private string GetMsgContent(IPipelineContext pipeLineContext, IBaseMessage baseMessage)
        {
            //extract message
            var message = new StringBuilder(); //Save message in this StringBuilder
            var copyStream = new VirtualStream(); //Copy the original stream to the a new stream 
            var copyStreamWriter = new StreamWriter(copyStream); //Use a stream writer to copy original stream to copyStream
            ReadOnlySeekableStream seekableStream = new ReadOnlySeekableStream(baseMessage.BodyPart.GetOriginalDataStream());

            //Read OriginalStream
            using (StreamReader streamReader = new StreamReader(seekableStream))
            {

                var line = string.Empty; //Read Stream Line by Line
                while ((line = streamReader.ReadLine()) != null)
                {
                    message.AppendLine(line); //Save Message to StringBuilder
                    copyStreamWriter.WriteLine(line); //Copy to new stream
                }

                copyStreamWriter.Flush(); //Ensure everything has been written to the new stream
                copyStream.Seek(0, SeekOrigin.Begin); //Set the new stream to the beginning
                baseMessage.BodyPart.Data = copyStream; //Set the Stream of the message leaving it's integrity intact

                pipeLineContext.ResourceTracker.AddResource(copyStream); //Ensure these resources are tracked and ultimately disposed
                pipeLineContext.ResourceTracker.AddResource(copyStreamWriter); //Ensure these resources are tracked and ultimately disposed

            }
            return message.ToString();
        }

        #endregion
    }
}
